local oo = require("SharedLibs.BaseLib");
local co = require("SharedLibs.CoroLib");
local CombinedOp = require("SharedLibs.CombinedOp");
Debugger = require("SharedLibs.Debugger");

AsyncOpState = require("SharedLibs.AsyncOpState");

Seq = require("SharedLibs.FP.Sequence");

_IsStudio= Debugger.IsStudio;

-----------
local Parallel = oo.Class()
Parallel.name = "Parallel"
oo.ClassExtends(Parallel, CombinedOp)

---
function co.Parallel(...)	
	local op = oo.New(Parallel, ...);
	return co.yield(op);
end

function co.NewParallel(...)
	return oo.New(Parallel, ...);
end

---
Parallel.RetFilterType = 
	{
		OpResults = 1, --- Note: not ordered by op index
		OrderedOpResults = 2, --- Order op result by Op index
	}

oo.SetDescriptor(
	Parallel.prototype,
	"destCount",
	{
		get = function(self)
			return self._destCount;
		end,
		set = function(self, destCount)
			self._destCount = destCount;
		end
	},
	true
)

function Parallel.prototype.constructor(self, ...)	
	Parallel._ss.prototype.constructor(self, ...)
	
	self:SetFilterType(Parallel.RetFilterType.OpResults);

	--- 0 means wait all op done
	self._destCount = 0;
	self._completeCount = 0;

	--- _retFilter: (completedOps: IAsyncOp[]) => any;
	self._retFilter = nil;
end

--- retFilter: (completedOps: IAsyncOp[]) => any
function Parallel.prototype.SetReturnFilter(self, retFilter)
	self._retFilter = retFilter;
	return self;
end

function Parallel.prototype.SetFilterType(self, filterType)
	filterType = filterType or Parallel.RetFilterType.OpResults;

	if filterType == Parallel.RetFilterType.OpResults then 
		self._retFilter = self._retOpResults;
	elseif filterType == Parallel.RetFilterType.OrderedOpResults then
		self._retFilter = self._orderedRetOpResults;
	end   
end

--- A Return Filter. return all completedOps's return value. 
--- ops: IAsyncOp[]
function Parallel.prototype._retOpResults(ops)
	local retValues = {};
	for i, v in ops
	do table.insert(retValues, v:GetReturn());
	end
	return retValues;
end

--- ops: IAsyncOp[]
function Parallel.prototype._orderedRetOpResults(self, ops)
	local retValues = {};
	for i, v in ops
	do
		local index = self:GetIndex(v);
		retValues[index] = v.GetReturn();
	end
	return retValues;
end

function Parallel.prototype:_onStart()
	self._completedOps = {};
	if 0 == #self._opList then
		self:_doStop(true);
		return;
	else
		assert(self._completeCount == 0);
		
		for i, op in ipairs(self._opList) do	
			Debugger.Assert(op.state == co.AsyncOpState.Ready
				, "AsyncOp's state is not Ready! AsyncOp: "..op:GetDesc()
					.." , current state: "..op.state);
			op:Start(self._onOpDone, self);

			if self._state == co.AsyncOpState.Stopped  then return end;
			if op:IsDone() then 
				if self._completeCount >= self:_getRealDestCount() then break end;
			end
		end		
	end

end

function Parallel.prototype:GetCurOps()
	return Seq.fromYield(function()
		for i, op in ipairs(self._opList) do
			if op.state == AsyncOpState.Executing or op.state == AsyncOpState.Paused then
				coroutine.yield(op);
			end
		end			
	end);
end

--- By default If destCount is 1, return the completed AsyncOp, or else return the completed AsyncOp list
--- If has custom return value filter(_retFilter), return filtered value.
function Parallel.prototype.GetReturn(self) 
	if (self._retFilter == nil) then
		if (1 == self.destCount) then
			Debugger.Assert(1 == #self._completedOps);
			return self._completedOps[0];

		else 
			return self._completedOps;
		end
	else
		return self._retFilter(self._completedOps);
	end
end

function Parallel.prototype:_onReset()
	self._completedOps = nil;
	self._completeCount = 0;	
	self:_resetOps();
end

function Parallel.prototype:_onOpDone(op) 

	if self._state == co.AsyncOpState.Stopped then return; end;

	Debugger.Assert(self._state ~= co.AsyncOpState.Ready);
	
	self._completeCount = self._completeCount + 1;

	table.insert(self._completedOps, op);

	Debugger.Assert(self._completeCount <= #self._opList);
	Debugger.Assert(self._completeCount <= self:_getRealDestCount());

	if self:_getRealDestCount() == self._completeCount then 
		self:_doStop(true);
	end
end

function Parallel.prototype._getRealDestCount(self)
	if 0 == self._destCount  then 
		return #self._opList
	else
		return self._destCount;
	end 
end

return Parallel;	